#include "metanet_module.h"
#include "metanet_malloc.h"

#include <dlfcn.h>

#include <assert.h>
#include <stdlib.h>
#include <stdint.h>
#include <stdio.h>
#include <string.h>

#define DL_CREATE "_create"
#define DL_INIT "_init"
#define DL_RELEASE "_release"
#define DL_SIGNAL "_signal"

#define MAX_MODULE_COUNT 32

struct metanet_modules {
    int count;
    const char *path;
    struct metanet_module m[MAX_MODULE_COUNT];
};

static struct metanet_modules *M = NULL;

static void *
_try_open(struct metanet_modules *mods, const char *name) {
    const char *l;
    const char *path = mods->path;
    size_t path_size = strlen(path);
    size_t name_size = strlen(name);

    //search path
    int sz = path_size + name_size;
    void *dl = NULL;
    char tmp[sz];
    do {
        memset(tmp, 0, sz);
        while (*path == ';') ++path;
        if (*path == '\0') break;
        l = strchr(path, ';');
        if (l == NULL) l = path + strlen(path);
        int len = l - path;
        int i;
        for (i = 0; path[i] != '?' && i < len; ++i) {
            tmp[i] = path[i];
        }
        memcpy(tmp+i, name, name_size);
        if (path[i] == '?') {
            strncpy(tmp+i+name_size, path+i+1, len-i-1);
        } else {
            fprintf(stderr, "Invalid C service pat\n");
        }
        dl = dlopen(tmp, RTLD_NOW | RTLD_GLOBAL);
        path = l;
    } while (dl == NULL);

    if (dl == NULL) {
        fprintf(stderr, "try open %s faild:%s\n", name, dlerror());
    }
    return dl;
}

static struct metanet_module *
_query(const char *name) {
    int i;
    for (i = 0; i < M->count; ++i) {
        if (strcmp(M->m[i].name, name) == 0) {
            return &M->m[i];
        }
    }
    return NULL;
}

static int
_open_sys(struct metanet_module *mod) {
    size_t name_size = strlen(mod->name);
    char tmp[name_size + 9]; // _create _init _release _signal
    memcpy(tmp, mod->name, name_size);
    strcpy(tmp+name_size, DL_CREATE);
    mod->create = dlsym(mod->dl, tmp);
    strcpy(tmp+name_size, DL_INIT);
    mod->init = dlsym(mod->dl, tmp);
    strcpy(tmp+name_size, DL_RELEASE);
    mod->release = dlsym(mod->dl, tmp);
    strcpy(tmp+name_size, DL_SIGNAL);
    mod->signal = dlsym(mod->dl, tmp);
    return mod->init == NULL;
}

void
metanet_module_insert(struct metanet_module *mod) {
    struct metanet_module *m = _query(mod->name);
    assert(m == NULL && M->count < MAX_MODULE_COUNT);
    int index = M->count;
    M->m[index] = *mod;
    ++M->count;
}

struct metanet_module *
metanet_module_query(const char *name) {
    struct metanet_module *result = _query(name);
    if (result) {
        return result;
    }

    if (M->count < MAX_MODULE_COUNT) {
        int index = M->count;
        void *dl = _try_open(M, name);
        if (dl) {
            M->m[index].name = name;
            M->m[index].dl = dl;
            if (_open_sys(&M->m[index]) == 0) {
                M->m[index].name = name;
                M->m[index].dl = dl;
                if (_open_sys(&M->m[index]) == 0) {
                    M->m[index].name = metanet_strdup(name);
                    M->count++;
                    result = &M->m[index];
                }
            }
        }
    }

    return result;
}

void *
metanet_module_instance_create(struct metanet_module *mod) {
    if (mod->create) {
        return mod->create();
    } else {
        return (void*)(intptr_t)(~0);
    }
}

int
metanet_module_instance_init(struct metanet_module *mod, void *inst, struct metanet_context *ctx, const char *param) {
    return mod->init(inst, ctx, param);
}

void 
metanet_module_instance_release(struct metanet_module *mod, void *inst) {
    if (mod->release) {
        mod->release(inst);
    }
}

void 
metanet_module_instance_siganl(struct metanet_module *mod, void *inst, int signal) {
    if (mod->signal) {
        mod->signal(inst, signal);
    }
}

void 
metanet_module_init(const char *path) {
    struct metanet_modules *m = metanet_malloc(sizeof(*m));
    m->count = 0;
    m->path = path;
    M = m;
}

void 
metanet_module_eixt() {
    if (M != NULL) {
        metanet_free(M);
    }
}